/*
 * FreeRTOS+TCP V2.3.2
 * Copyright (C) 2020 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * http://aws.amazon.com/freertos
 * http://www.FreeRTOS.org
 */

/* FreeRTOS includes. */
#include "FreeRTOS.h"
#include "list.h"
#include "queue.h"
#include "semphr.h"
#include "task.h"

/* FreeRTOS+TCP includes. */
#include "FreeRTOS_IP.h"
#include "FreeRTOS_Sockets.h"
#include "FreeRTOS_IP_Private.h"
#include "NetworkBufferManagement.h"
#include "NetworkInterface.h"


#include "m480_eth.h"

/* If ipconfigETHERNET_DRIVER_FILTERS_FRAME_TYPES is set to 1, then the Ethernet
 * driver will filter incoming packets and only pass the stack those packets it
 * considers need processing. */
#if ( ipconfigETHERNET_DRIVER_FILTERS_FRAME_TYPES == 0 )
    #define ipCONSIDER_FRAME_FOR_PROCESSING( pucEthernetBuffer )    eProcessBuffer
#else
    #define ipCONSIDER_FRAME_FOR_PROCESSING( pucEthernetBuffer )    eConsiderFrameForProcessing( ( pucEthernetBuffer ) )
#endif

/* Default the size of the stack used by the EMAC deferred handler task to twice
 * the size of the stack used by the idle task - but allow this to be overridden in
 * FreeRTOSConfig.h as configMINIMAL_STACK_SIZE is a user definable constant. */
#ifndef configEMAC_TASK_STACK_SIZE
    #define configEMAC_TASK_STACK_SIZE    ( 2 * configMINIMAL_STACK_SIZE )
#endif


static SemaphoreHandle_t xTXMutex = NULL;

/* The handle of the task that processes Rx packets.  The handle is required so
 * the task can be notified when new packets arrive. */
static TaskHandle_t xRxHanderTask = NULL;
static TimerHandle_t xPhyHandlerTask = NULL;

/*
 * A task that processes received frames.
 */
static void prvEMACHandlerTask( void * pvParameters );
static void prvPhyTmrCallback( TimerHandle_t xTimer );

/* The size of each buffer when BufferAllocation_1 is used:
 * http://www.freertos.org/FreeRTOS-Plus/FreeRTOS_Plus_TCP/Embedded_Ethernet_Buffer_Management.html */

#define niBUFFER_1_PACKET_SIZE    1536
#ifdef __ICCARM__
    #pragma data_alignment=4
    static uint8_t ucNetworkPackets[ ipconfigNUM_NETWORK_BUFFER_DESCRIPTORS * niBUFFER_1_PACKET_SIZE ]
#else
    static uint8_t ucNetworkPackets[ ipconfigNUM_NETWORK_BUFFER_DESCRIPTORS * niBUFFER_1_PACKET_SIZE ] __attribute__( ( aligned( 4 ) ) );
#endif

BaseType_t xNetworkInterfaceInitialise( void )
{
    uint8_t hwaddr[ 6 ];
    BaseType_t xReturn = pdPASS;

    /* Init ETH */
    numaker_mac_address( hwaddr );
    FreeRTOS_UpdateMACAddress( hwaddr );
    FreeRTOS_printf( ( "mac address %02x-%02x-%02x-%02x-%02x-%02x \r\n", hwaddr[ 0 ], hwaddr[ 1 ], hwaddr[ 2 ], hwaddr[ 3 ], hwaddr[ 4 ], hwaddr[ 5 ] ) );

    /* Enable clock & set EMAC configuration         */
    /* Enable MAC and DMA transmission and reception */
    if( numaker_eth_init( hwaddr ) < 0 )
    {
        xReturn = pdFAIL;
    }
    else
    {
        xReturn = pdPASS;

        /* Guard against the task being created more than once and the
         * descriptors being initialized more than once. */
        /* Timer task to monitor PHY Link status */
        if( xPhyHandlerTask == NULL )
        {
            xPhyHandlerTask = xTimerCreate( "TimerPhy", pdMS_TO_TICKS( 1000 ), pdTRUE, 0, prvPhyTmrCallback );
            configASSERT( xPhyHandlerTask );
            xReturn = xTimerStart( xPhyHandlerTask, 0 );
            configASSERT( xReturn );
        }

        /* Rx task */
        if( xRxHanderTask == NULL )
        {
            xReturn = xTaskCreate( prvEMACHandlerTask, "EMAC", configEMAC_TASK_STACK_SIZE, NULL, configMAX_PRIORITIES - 1, &xRxHanderTask );
            configASSERT( xReturn );
        }

        if( xTXMutex == NULL )
        {
            xTXMutex = xSemaphoreCreateMutex();
            configASSERT( xTXMutex );
        }
    }

    NVIC_SetPriority( EMAC_RX_IRQn, configMAC_INTERRUPT_PRIORITY );
    NVIC_SetPriority( EMAC_TX_IRQn, configMAC_INTERRUPT_PRIORITY );

    numaker_eth_enable_interrupts();

    FreeRTOS_printf( ( "ETH-RX priority:%d\n", NVIC_GetPriority( EMAC_RX_IRQn ) ) );

    return xReturn;
}

BaseType_t xNetworkInterfaceOutput( NetworkBufferDescriptor_t * const pxDescriptor,
                                    BaseType_t xReleaseAfterSend )
{
    uint8_t * buffer = NULL;

    if( pxDescriptor->xDataLength >= PACKET_BUFFER_SIZE )
    {
        FreeRTOS_printf( ( "TX buffer length %d over %d\n", pxDescriptor->xDataLength, PACKET_BUFFER_SIZE ) );
        return pdFALSE;
    }

    buffer = numaker_eth_get_tx_buf();

    if( buffer == NULL )
    {
        NU_DEBUGF( ( "Eth TX slots are busy\n" ) );
        return pdFALSE;
    }

    /* Get exclusive access */
    xSemaphoreTake( xTXMutex, portMAX_DELAY );
    NU_DEBUGF( ( "%s ... buffer=0x%x\r\n", __FUNCTION__, buffer ) );
    /*SendData: pt = pxDescriptor->pucBuffer, length = pxDescriptor->xDataLength */
    memcpy( buffer, pxDescriptor->pucEthernetBuffer, pxDescriptor->xDataLength );
    numaker_eth_trigger_tx( pxDescriptor->xDataLength, NULL );
    /* Call the standard trace macro to log the send event. */
    iptraceNETWORK_INTERFACE_TRANSMIT();

    if( xReleaseAfterSend != pdFALSE )
    {
        /* It is assumed SendData() copies the data out of the FreeRTOS+TCP Ethernet
         * buffer.  The Ethernet buffer is therefore no longer needed, and must be
         * freed for re-use. */
        vReleaseNetworkBufferAndDescriptor( pxDescriptor );
    }

    xSemaphoreGive( xTXMutex );

    return pdTRUE;
}


void vNetworkInterfaceAllocateRAMToBuffers( NetworkBufferDescriptor_t pxNetworkBuffers[ ipconfigNUM_NETWORK_BUFFER_DESCRIPTORS ] )
{
    uint8_t * ucRAMBuffer = ucNetworkPackets;
    uint32_t ul;

    for( ul = 0; ul < ipconfigNUM_NETWORK_BUFFER_DESCRIPTORS; ul++ )
    {
        pxNetworkBuffers[ ul ].pucEthernetBuffer = ucRAMBuffer + ipBUFFER_PADDING;
        *( ( unsigned * ) ucRAMBuffer ) = ( unsigned ) ( &( pxNetworkBuffers[ ul ] ) );
        ucRAMBuffer += niBUFFER_1_PACKET_SIZE;
    }
}


BaseType_t xGetPhyLinkStatus( void )
{
    BaseType_t xReturn;

    if( numaker_eth_link_ok() )
    {
        xReturn = pdPASS;
    }
    else
    {
        xReturn = pdFAIL;
    }

    return xReturn;
}

static void prvPhyTmrCallback( TimerHandle_t xTimer )
{
    IPStackEvent_t xRxEvent;
    static BaseType_t lastLink = pdFAIL;
    BaseType_t currLink = xGetPhyLinkStatus();

    if( currLink != lastLink )
    {
        FreeRTOS_printf( ( "PHY Link %s\n", ( currLink ) ? "Up" : "Down" ) );

        if( !currLink )
        {
            xRxEvent.eEventType = eNetworkDownEvent;
            xSendEventStructToIPTask( &xRxEvent, 0 );
        }

        lastLink = currLink;
    }
}


static void prvEMACHandlerTask( void * pvParameters )
{
    TimeOut_t xPhyTime;
    TickType_t xPhyRemTime;
    UBaseType_t uxLastMinBufferCount = 0;
    UBaseType_t uxCurrentCount;
    BaseType_t xResult = 0;
    uint32_t ulStatus;
    uint16_t dataLength = 0;
    uint8_t * buffer = NULL;
    NetworkBufferDescriptor_t * pxBufferDescriptor = NULL;
    IPStackEvent_t xRxEvent;
    const TickType_t xBlockTime = pdMS_TO_TICKS( 5000ul );

    /* Remove compiler warnings about unused parameters. */
    ( void ) pvParameters;
    /* A possibility to set some additional task properties. */

    for( ; ; )
    {
        uxCurrentCount = uxGetMinimumFreeNetworkBuffers();

        if( uxLastMinBufferCount != uxCurrentCount )
        {
            /* The logging produced below may be helpful
             * while tuning +TCP: see how many buffers are in use. */
            uxLastMinBufferCount = uxCurrentCount;
            FreeRTOS_printf( ( "Network buffers: %lu lowest %lu\n",
                               uxGetNumberOfFreeNetworkBuffers(), uxCurrentCount ) );
        }

        /* No events to process now, wait for the next. */
        ulTaskNotifyTake( pdFALSE, portMAX_DELAY );

        while( 1 )
        {
            /* get received frame */
            if( numaker_eth_get_rx_buf( &dataLength, &buffer ) != 0 )
            {
                /* The event was lost because a network buffer was not available.
                 * Call the standard trace macro to log the occurrence. */
                iptraceETHERNET_RX_EVENT_LOST();
                break;
            }

            /* Allocate a network buffer descriptor that points to a buffer
             * large enough to hold the received frame.  As this is the simple
             * rather than efficient example the received data will just be copied
             * into this buffer. */

            pxBufferDescriptor = pxGetNetworkBufferWithDescriptor( PACKET_BUFFER_SIZE, 0 );

            if( pxBufferDescriptor != NULL )
            {
                memcpy( pxBufferDescriptor->pucEthernetBuffer, buffer, dataLength );
                pxBufferDescriptor->xDataLength = dataLength;
            }
            else
            {
                numaker_eth_rx_next();
                iptraceETHERNET_RX_EVENT_LOST();
                break;
            }

            /* The event about to be sent to the TCP/IP is an Rx event. */
            xRxEvent.eEventType = eNetworkRxEvent;

            /* pvData is used to point to the network buffer descriptor that
             *  now references the received data. */
            xRxEvent.pvData = ( void * ) pxBufferDescriptor;

            /* Send the data to the TCP/IP stack. */
            if( xSendEventStructToIPTask( &xRxEvent, 0 ) == pdFALSE )
            {
                /* The buffer could not be sent to the IP task so the buffer
                 * must be released. */
                vReleaseNetworkBufferAndDescriptor( pxBufferDescriptor );

                /* Make a call to the standard trace macro to log the
                 *      occurrence. */

                iptraceETHERNET_RX_EVENT_LOST();
            }
            else
            {
                /* The message was successfully sent to the TCP/IP stack.
                * Call the standard trace macro to log the occurrence. */
                iptraceNETWORK_INTERFACE_RECEIVE();
            }

            numaker_eth_rx_next();
        }

        numaker_eth_trigger_rx();
    }
}

void xNetworkCallback( char event )
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;

    switch( event )
    {
        case 'R': /*For RX event */

            /* Wakeup the prvEMACHandlerTask. */
            if( xRxHanderTask != NULL )
            {
                vTaskNotifyGiveFromISR( xRxHanderTask, &xHigherPriorityTaskWoken );
                portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
            }

            break;

        case 'T': /*For TX event */
            /* ack of tx done, no-op in this stage */
            break;

        default:
            break;
    }
}
